package com.odata.springboot.starter.odata.odata.config;

import com.google.common.collect.Lists;
import com.odata.springboot.starter.odata.annotation.OdataEdmEntity;
import com.odata.springboot.starter.odata.edmprovider.SimpleEdmProvider;
import com.odata.springboot.starter.odata.odata.processor.SimpleEntityCollectionProcessor;
import com.odata.springboot.starter.odata.odata.processor.SimpleEntityProcessor;
import com.odata.springboot.starter.odata.odata.processor.SimplePrimitiveProcessor;
import com.odata.springboot.starter.odata.odata.sql.sqlBuild.OdataClassMetaData;
import com.odata.springboot.starter.odata.packagesacn.ClasspathPackageScanner;
import com.odata.springboot.starter.odata.springaware.ODataSpringContext;
import com.odata.springboot.starter.odata.utils.OdataJdbcUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.olingo.commons.api.edm.FullQualifiedName;
import org.apache.olingo.commons.api.edm.provider.*;
import org.apache.olingo.server.api.OData;
import org.apache.olingo.server.api.ODataHttpHandler;
import org.apache.olingo.server.api.ServiceMetadata;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * odata配置
 *
 * @author chenYX
 * @date 2021/6/30 16:57
 * @description
 */
@Slf4j
@Configuration
@ConditionalOnBean(DataSource.class)
@Component
public class OdataConfig implements InitializingBean {

    /**
     * odataconfig
    */
    @Autowired
    private OdataBaseConfigInformation odataBaseConfigInformation;

    /**
     * odata 读取多条数据处理器
     */
    @Autowired
    private SimpleEntityCollectionProcessor simpleEntityCollectionProcessor;

    /**
     * odata 依据主键处理数据
     */
    @Autowired
    private SimpleEntityProcessor simpleEntityProcessor;

    /**
     * odata 依据主键处理数据
     */
    @Autowired
    private SimplePrimitiveProcessor simplePrimitiveProcessor;

    /**
     * odata edm 元数据
     */
    @Autowired
    private SimpleEdmProvider simpleEdmProvider;

    /**
     * 数据源
    */
    private DataSource dataSource;

    /**
     * OdataJdbcUtils 执行sql语句
     */
    @Autowired
    private OdataJdbcUtils odataJdbcUtils;

    @Autowired
    private ODataSpringContext oDataSpringContext;

    /**
     * entityset 后缀标识
     */
    private static String ENTITY_SET_SUFFIX = "s";

    private CsdlEntityContainer csdlEntityContainer;

    private List<CsdlSchema> schemaList;

    private FullQualifiedName containerFullQualifiedName;


    /**
     * odata 容器bean
     *
     * @param
     * @author : yongxiang
     * @description :
     * @date : 16:59 2021/6/30
     */

    public CsdlEntityContainer csdlEntityContainer() throws Exception {
        CsdlEntityContainer csdlEntityContainer = new CsdlEntityContainer();
        //容器名称
        csdlEntityContainer.setName(odataBaseConfigInformation.getContainerName());
        initCachedData();
        //获取entityset
        List<CsdlEntitySet> csdlEntitySets = Lists.newArrayList(OdataCachedData.cachedCsdlEntitySetMap.values());
        csdlEntityContainer.setEntitySets(csdlEntitySets);
        return csdlEntityContainer;
    }

    public List<CsdlSchema> schemaList(CsdlEntityContainer csdlEntityContainer) {
        CsdlSchema csdlSchema = new CsdlSchema();
        csdlSchema.setNamespace(odataBaseConfigInformation.getNameSpace());
        //addContainer
        csdlSchema.setEntityContainer(csdlEntityContainer);
        //addEntityTypes
        ArrayList<CsdlEntityType> csdlEntityTypes = Lists.newArrayList(OdataCachedData.cachedCsdlEntityTypeMap.values());
        csdlSchema.setEntityTypes(csdlEntityTypes);
        ArrayList<CsdlSchema> csdlSchemas = new ArrayList<>();
        csdlSchemas.add(csdlSchema);
        return csdlSchemas;
    }


    /**
     * 初始化odata的缓存数据
     *
     * @param
     * @author : yongxiang
     * @description :
     * @date : 17:17 2021/6/30
     */
    private void initCachedData() throws Exception {
        //获取要扫描的包
        List<String> edmEntityPackages = odataBaseConfigInformation.getEdmEntityPackages();
        if (CollectionUtils.isNotEmpty(edmEntityPackages)){
            for (String edmEntityPackage : edmEntityPackages) {
                List<String> classNameList = ClasspathPackageScanner.getFullyQualifiedClassNameList(edmEntityPackage);
                if (CollectionUtils.isNotEmpty(classNameList)) {
                    for (String className : classNameList) {
                        Class<?> aClass = Class.forName(className);
                        //是否有edmEntity注解
                        OdataEdmEntity odataEdmEntity = aClass.getAnnotation(OdataEdmEntity.class);
                        if (odataEdmEntity != null) {
                            //加入数据到缓存
                            addCachedData(aClass);
                        }
                    }
                }
            }
        }
    }

    /**
     * 依据一个类创建缓存数据
     *
     * @param
     * @author : yongxiang
     * @description :
     * @date : 17:28 2021/6/30
     */
    private void addCachedData(Class<?> aClass) {
        //加入edmEntityClass
        OdataCachedData.emdEntitySet.add(aClass);
        //元数据
        OdataClassMetaData odataClassMetaData = new OdataClassMetaData(aClass);
        OdataCachedData.cachedCsdlMetaDataMap.put(aClass.getName(), odataClassMetaData);
        //创建csdlEntitySet 并加入缓存
        CsdlEntitySet csdlEntitySet = buildCsdlEntityset(aClass);
        OdataCachedData.cachedCsdlEntitySetMap.put(getEntitySetName(aClass), csdlEntitySet);
        //创建csdlEntityType
        CsdlEntityType csdlEntityType = createCsdlEntityTypeMap(aClass);
        if (csdlEntityType != null) {
            OdataCachedData.cachedCsdlEntityTypeMap.put(aClass.getName(), csdlEntityType);
        }
    }

    /**
     * 创建 CsdlEntityType
     *
     * @param
     * @author : yongxiang
     * @description :
     * @date : 17:41 2021/6/30
     */
    private CsdlEntityType createCsdlEntityTypeMap(Class<?> aClass) {
        OdataEdmEntity odataEdmEntity = aClass.getAnnotation(OdataEdmEntity.class);
        if (odataEdmEntity != null) {
            //获取该表的元数据 --
            CsdlEntityType csdlEntityType = odataJdbcUtils.getEntityTypeByClass(aClass);
            if (csdlEntityType != null){
                csdlEntityType.setName(aClass.getSimpleName());
                //设置主键
                OdataClassMetaData classMetaData = OdataCachedData.getClassMetaDataByClass(aClass);
                if (classMetaData != null){
                    String primaryKeyFieldName = classMetaData.getPrimaryKeyField();
                    if (StringUtils.isNotBlank(primaryKeyFieldName)){
                        CsdlPropertyRef csdlPropertyRef = new CsdlPropertyRef();
                        csdlPropertyRef.setName(primaryKeyFieldName);
                        csdlEntityType.setKey(Collections.singletonList(csdlPropertyRef));
                    }
                }
                return csdlEntityType;
            }
        }
        return null;
    }

    /**
     * 创建CsdlEntityset
     *
     * @param
     * @author : yongxiang
     * @description :
     * @date : 17:40 2021/6/30
     */
    private CsdlEntitySet buildCsdlEntityset(Class<?> aClass) {
        CsdlEntitySet csdlEntitySet = new CsdlEntitySet();
        //设置名称
        csdlEntitySet.setName(getEntitySetName(aClass));
        //设置类型 -- 类simple名称加s
        csdlEntitySet.setType(new FullQualifiedName(aClass.getName()));
        return csdlEntitySet;
    }


    @Override
    public void afterPropertiesSet() throws Exception {
        //初始化数据源
        initDataSource();
        //初始化edm 容器
        final CsdlEntityContainer csdlEntityContainer = csdlEntityContainer();
        this.containerFullQualifiedName = new FullQualifiedName(odataBaseConfigInformation.getNameSpace(), csdlEntityContainer.getName());
        this.setCsdlEntityContainer(csdlEntityContainer);
        this.setSchemaList(schemaList(csdlEntityContainer));
    }

    /**
     * @Description 初始化数据源
     * @Date 2021/7/3 18:26
     * @Param
     * @return
     */
    private void initDataSource() {
        String dataSourceName = odataBaseConfigInformation.getDataSourceName();
        if (StringUtils.isNotBlank(dataSourceName)){
            Object dataSource = oDataSpringContext.getBean(dataSourceName);
            if (dataSource == null || !(dataSource instanceof DataSource)){
                throw new RuntimeException("odata数据源设置失败");
            }
            this.dataSource = (DataSource) dataSource;
        }else {
            DataSource dataSource = oDataSpringContext.getBean(DataSource.class);
            if (dataSource == null){
                throw new RuntimeException("odata数据源设置失败");
            }
            this.dataSource = dataSource;
        }
    }


    @Bean
    public ODataHttpHandler oData() {
        OData oData = OData.newInstance();
        ServiceMetadata serviceMetadata = oData.createServiceMetadata(simpleEdmProvider, new ArrayList<>());
        ODataHttpHandler handler = oData.createHandler(serviceMetadata);
        handler.register(simpleEntityProcessor);
        handler.register(simpleEntityCollectionProcessor);
        handler.register(simplePrimitiveProcessor);
        return handler;
    }

    public CsdlEntityContainer getCsdlEntityContainer() {
        return csdlEntityContainer;
    }

    public void setCsdlEntityContainer(CsdlEntityContainer csdlEntityContainer) {
        this.csdlEntityContainer = csdlEntityContainer;
    }

    public List<CsdlSchema> getSchemaList() {
        return schemaList;
    }

    public void setSchemaList(List<CsdlSchema> schemaList) {
        this.schemaList = schemaList;
    }


    public FullQualifiedName getContainerFullQualifiedName() {
        return containerFullQualifiedName;
    }

    public void setContainerFullQualifiedName(FullQualifiedName containerFullQualifiedName) {
        this.containerFullQualifiedName = containerFullQualifiedName;
    }

    public DataSource getDataSource() {
        return dataSource;
    }


    private String getEntitySetName(Class aClass) {
        return aClass.getSimpleName() + ENTITY_SET_SUFFIX;
    }

}
